//
// compile.c
// ast to bytecode
//
// (c) 2008 why the lucky stiff, the freelance professor
//
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
// #include <assert.h>
#include "always_assert.h"
#include "potion.h"
#include "internal.h"
#include "ast.h"
#include "opcodes.h"
#include "asm.h"

// 二进制的标头，柔性数组成员是 proto 的序列化串。
//  放在这里避免找不到 potion.h 中的 POTION_SIG_LEN （只在编辑器中提示错误）。
struct PNBHeader {
  u8 sig[POTION_SIG_LEN]; // 签名串
  u8 major;
  u8 minor;
  u8 vmid;
  u8 pn;                  // 类型 PN 尺寸
  u8 proto[0];            // PNProto
};

// 写入（追加）汇编指令：一个 PN_OP 结构，指令+单参 。按需扩容。
#define PN_ASM1(ins, _a)     f->ops = (PN)potion_asm_op(P, (PNAsm *)f->ops, ins, (int)_a, 0)
// 写入（追加）汇编指令：一个 PN_OP 结构，指令+双参 。按需扩容。
#define PN_ASM2(ins, _a, _b) f->ops = (PN)potion_asm_op(P, (PNAsm *)f->ops, ins, (int)_a, (int)_b)

const struct {
  const char *name; // 操作码名
  const u8 args;    // 操作码参数数量
} potion_ops[] = {
  {"noop", 0}, {"move", 2}, {"loadval", 2}, {"loadpn", 2}, {"self", 1},
  {"newtuple", 1}, {"settuple", 2}, {"getlocal", 2}, {"setlocal", 2},
  {"getupval", 2}, {"setupval", 2}, {"gettable", 2}, {"settable", 2},
  {"newlick", 2}, {"getpath", 2}, {"setpath", 2}, {"add", 2}, {"sub", 2},
  {"mult", 2}, {"div", 2}, {"mod", 2}, {"pow", 2}, {"not", 1}, {"cmp", 2},
  {"eq", 2}, {"neq", 2}, {"lt", 2}, {"lte", 2}, {"gt", 2}, {"gte", 2},
  {"bitn", 1}, {"bitl", 2}, {"bitr", 2}, {"pipe", 2}, {"caret", 2}, {"amp", 2},
  {"def", 2}, {"bind", 2}, {"message", 2},
  {"jump", 2}, {"test", 1}, {"testjmp", 2}, {"notjmp", 2}, {"named", 2},
  {"call", 2}, {"callset", 2}, {"tailcall", 2}, {"return", 1},
  {"proto", 2}, {"class", 2}
};

// 取 PNProto 的抽象语法树 .tree 。
PN_MSG(potion_proto_tree) {
  return PN_PROTO(self)->tree;
}

// 以 Lobby 为命名空间调用 PNProto self ，传入参数 args ，返回其结果。
//    主要用于 call 消息。
PN_MSG(potion_proto_call, PN args) {
  return potion_vm(P, self, P->lobby, args, 0, NULL);
}

#define T(s)  PN_AST_TYPE(s)
#define A1(s) PN_AST_ARG((s), 0)
#define A2(s) PN_AST_ARG((s), 1)
#define AT(n) PN_AST_ARG(t, n)
#define AT1   PN_AST_ARG(t, 0)
#define AT2   PN_AST_ARG(t, 1)
#define AT3   PN_AST_ARG(t, 2)

// 字符串化 PNProto ，依次为：内存地址、指令字节数、参数表、寄存器数；
//  各类列表：.path .local .upval .value ，各项的值和索引；
//  子 PNProto；
//  操作码列表：编号 名 参数a 参数b，以及 3 个跳转指令的偏移(jmp、notjmp、testjmp)、
//    立即数值(loadpn)、索引值(loadval)、局部变量名(setlocal、getlocal)
// TODO hsq 这个指令格式不够直观，改成代码表达式一样的，
//  如 loadval 1 0->reg[1]=.value[0](实际值), getlocal 2 0->reg[2]=local[0] 等。
PN_MSG(potion_proto_string) {
  vPN(Proto) t = (struct PNProto *)self;
  PN_SIZE x   = 0;
  PN_SIZE num = 1;
  PN_SIZE numcols;
  u8 lastarg = PN_TUPLE_LEN(t->sig) - 1;
  PN out = potion_bytes_str(P, "; function definition");
  potion_bytes_format(P, out, ": %p ; %u bytes\n", t, PN_FLEX_SIZE(t->ops));
  POTION_BYTES_LITERAL(out, "; (");
  PN_TUPLE_EACH(t->sig, i, v, {
    if (PN_IS_NUM(v)) {
      if (v == '.')
        POTION_BYTES_LITERAL(out, ". ");
      else if (v == '|')
        POTION_BYTES_LITERAL(out, "| ");
      else if (i < lastarg)
        potion_bytes_format(P, out, "=%c, ", (int)PN_INT(v));
      else
        potion_bytes_format(P, out, "=%c", (int)PN_INT(v));
    } else
      potion_bytes_obj_string(P, out, v);
  });
  potion_bytes_format(P, out, ") %ld registers\n", t->stacksize);
  PN_TUPLE_EACH(t->paths, i, v, {
    POTION_BYTES_LITERAL(out, ".path /");
    v = PN_TUPLE_AT(t->values, PN_INT(v));
    potion_bytes_obj_string(P, out, v);
    potion_bytes_format(P, out, " ; %u\n", i);
  });
  PN_TUPLE_EACH(t->locals, i, v, {
    POTION_BYTES_LITERAL(out, ".local ");
    potion_bytes_obj_string(P, out, v);
    potion_bytes_format(P, out, " ; %u\n", i);
  });
  PN_TUPLE_EACH(t->upvals, i, v, {
    POTION_BYTES_LITERAL(out, ".upval ");
    potion_bytes_obj_string(P, out, v);
    potion_bytes_format(P, out, " ; %u\n", i);
  });
  PN_TUPLE_EACH(t->values, i, v, {
    POTION_BYTES_LITERAL(out, ".value ");
    potion_bytes_obj_string(P, out, v);
    potion_bytes_format(P, out, " ; %u\n", i);
  });
  PN_TUPLE_EACH(t->protos, i, v, {
    potion_bytes_obj_string(P, out, v);
  });
  numcols = (int)ceil(log10(PN_OP_LEN(t)));
  for (x = 0; x < PN_OP_LEN(t); x++) {
    const int commentoffset = 20;
    // TODO hsq 直接打印操作码名和首参，不可用于无参操作码（如 OP_NONE ）。
    int width = potion_bytes_format(P, out, "[%*u] %-8s %d",
      numcols, num, potion_ops[PN_OP_AT(t, x).code].name, PN_OP_AT(t, x).a);

    if (potion_ops[PN_OP_AT(t, x).code].args > 1)
      width += potion_bytes_format(P, out, " %d", PN_OP_AT(t, x).b);

    if (width < commentoffset)
      potion_bytes_format(P, out, "%*s", commentoffset - width, "");
    else
      POTION_BYTES_LITERAL(out, " ");

    // TODO: Byte code listing: instead of using tabs, pad with spaces to make everything line up
    switch (PN_OP_AT(t, x).code) {
      case OP_JMP:
        potion_bytes_format(P, out, "; to %d", num + PN_OP_AT(t, x).b + 1);
        break;
      case OP_NOTJMP:
      case OP_TESTJMP:
        potion_bytes_format(P, out, "; to %d", num + PN_OP_AT(t, x).b + 1);
        break;
      case OP_LOADPN:
        POTION_BYTES_LITERAL(out, "; ");
        potion_bytes_obj_string(P, out, PN_OP_AT(t, x).b);
        break;
      case OP_LOADVAL:
        POTION_BYTES_LITERAL(out, "; ");
        potion_bytes_obj_string(P, out, PN_TUPLE_AT(t->values, PN_OP_AT(t, x).b));
        break;
      case OP_SETLOCAL:
      case OP_GETLOCAL:
        POTION_BYTES_LITERAL(out, "; ");
        potion_bytes_obj_string(P, out, PN_TUPLE_AT(t->locals, PN_OP_AT(t, x).b));
        break;
    }
    POTION_BYTES_LITERAL(out, "\n");
    num++;
  }
  POTION_BYTES_LITERAL(out, "; function end\n");
  return PN_STR_B(out);
}

#if POTION_JIT == 1
// 反汇编：将 PNProto self 的机器码 dump 到 /tmp/potion_jit_disasm.o ，
//    再 反汇编到 /tmp/potion_jit_disasm.s ；返回是否成功。
// TODO hsq 依赖 ndisasm ，并用 bat 输出到控制台；写死了 64 位。
PN_MSG(potion_jit_disasm) {
  #define FILENAME "/tmp/potion_jit_disasm"
  vPN(Proto) f = PN_PROTO(self);
  PN_F jit;
  PN_SIZE len;
  if (!f->jit) return PN_NIL;
  len = JIT_LEN(f->jit);
  jit = JIT_CODE(f->jit);
  FILE *fh = fopen(FILENAME ".o", "wb");
  assert(fh);
  assert(fwrite(jit, 1, len, fh) == len);
  assert(!fclose(fh));
  system("ndisasm -b 64 " FILENAME ".o > " FILENAME ".s; bat " FILENAME ".s");
  return PN_TRUE;
  #undef FILENAME
}
#endif

// 根据当前寄存器编号 reg ，记录 PNProto f 中所需的寄存器数量（栈尺寸）
#define PN_REG(f, reg) \
  if ((PN_SIZE)reg >= f->stacksize) \
    f->stacksize = reg + 1
// 编译 PNSource 当前节点 t 的索引 n 处的参数：直接编译该参数，除非
//    该参数是表达式且其子项是表，则遍历该表编译每一项。
#define PN_ARG(n, reg) \
  if (T(AT(n)) == AST_EXPR && T(PN_TUPLE_AT(A1(AT(n)), 0)) == AST_TABLE) { \
    PN test = A1(PN_TUPLE_AT(A1(AT(n)), 0)); \
    if (!PN_IS_NIL(test)) { \
      PN_TUPLE_EACH(test, i, v, { \
        potion_source_asmb(P, f, loop, 0, (struct PNSource *)v, reg); }); \
    } \
  } else { \
    potion_source_asmb(P, f, loop, 0, (struct PNSource *)AT(n), reg); \
  }
// 编译 f 的子函数， f 为后者的 .source ；子函数的每个 upval ，
//  根据在 f 中是 upval 还是 local 分别生成 GETUPVAL 或 GETLOCAL ，
//    用于执行 OP_PROTO 构造闭包时，从 f 的 upval 和 local 中获得参数来设置前者的 upval ，
//    这些数据指令的 .a 为闭包所在的寄存器号。
// TODO hsq 使用别的方式传输 upval ，而不是指令？则 potion_target_op_proto 的签名也可与其他函数相同。
// TODO hsq 参数顺序与 AST_PROTO 不同。
#define PN_BLOCK(reg, blk, sig) ({ \
  PN block = potion_send(blk, $(compile), (PN)f, sig); \
  HAS_UPVALS_SET(f, UPVALSIZE((struct PNProto *)block)); \
  PN_SIZE num = PN_PUT(f->protos, block); \
  PN_ASM2(OP_PROTO, reg, num); \
  PN_TUPLE_EACH(((struct PNProto *)block)->upvals, i, v, { \
    PN_SIZE numup = PN_GET(f->upvals, v); \
    if (numup != PN_NONE) PN_ASM2(OP_GETUPVAL, reg, numup); \
    else                  PN_ASM2(OP_GETLOCAL, reg, PN_GET(f->locals, v)); \
  }); \
})
// 查找名为 name 的 upval ：如是本地变量或找不到则返回 none ，如在本地 .upvals 中则返回其索引，
//    否则向上逐级查找各 .locals ，如在某级找到则加入各级的 .upvals 中。
// TODO hsq upval 优先于本地变量？无显式变量声明，则不可用与外层变量同名的本地变量？
// TODO hsq 向上查找时如果不搜索 .upvals ，则逐级加入用处不大？且可能重复加入？
#define PN_UPVAL(name) ({ \
  PN_SIZE numl = PN_GET(f->locals, name); \
  PN_SIZE numup = PN_NONE; \
  if (numl == PN_NONE) { \
    numup = PN_GET(f->upvals, name); \
    if (numup == PN_NONE) { \
      vPN(Proto) up = f; \
      int depth = 1; \
      while (PN_IS_PROTO(up->source)) { \
        up = (struct PNProto *)up->source; \
        if (PN_NONE != (numup = PN_GET(up->locals, name))) break; \
        depth++; \
      } \
      if (numup != PN_NONE) { \
        up = f; \
        while (depth--) { \
          up->upvals = PN_PUSH(up->upvals, name); \
          up = (struct PNProto *)up->source; \
        } \
      } \
      numup = PN_GET(f->upvals, name); \
    } \
  } \
  numup; \
})
// 编译参数表 args ，实际都传入 .a[1] ，也可为 nil 或其他类型。
//  如果 inc 则每项对应 reg 后的连续位置 ，否则对应同一个。
// inc=1 时: reg-1: cl, reg: self, reg+1: 无名参数顺次入栈...,
//  reg+ARGS_NUM+1: 命名参数的名和值顺次入栈... 。
//  无名参数编译时处理，命名的则运行时，所以无论代码中位置如何，都会覆盖无名参数。
// TODO hsq reg 当前有值且不可覆盖（如 self ）才需要 inc=1 ？
#define PN_ARG_TABLE(args, reg, inc) potion_arg_asmb(P, f, loop, args, &reg, inc)

#define MAX_JUMPS 1024
// 记录循环体中直接的 break 和 continue 对应的 JMP 指令（不含子循环中的）。
// bXXX 用于 break ， cXXX 用于 continue 。
// TODO hsq 使用 tuple 代替？自动扩容，无需检查容量。
struct PNLoop {
  int bjmps[MAX_JUMPS];
  int cjmps[MAX_JUMPS];

  int bjmpc;
  int cjmpc;
};

void potion_source_asmb(Potion *, vPN(Proto), struct PNLoop *, PN_SIZE, vPN(Source), u8);

// 同 PN_ARG_TABLE 。
void potion_arg_asmb(Potion *P, vPN(Proto) f, struct PNLoop *loop, PN args, u8 *reg, int inc)
{
  if (args != PN_NIL) {
    if (T(args) == AST_TABLE) {
      args = A1(args);
      if (!PN_IS_NIL(args)) {
        u8 freg = *reg, sreg = *reg + PN_TUPLE_LEN(args) + 1;
        PN_TUPLE_EACH(args, i, v, {
          if (inc) {
            (*reg)++; // TODO hsq 只影响下面的非 AST_ASSIGN 分支，简化整个函数？
            if (T(v) == AST_ASSIGN) {
              vPN(Source) lhs = (struct PNSource *)A1(v);
              potion_source_asmb(P, f, loop, 0, (struct PNSource *)A2(v), sreg + 1);
              if (T(lhs) == AST_EXPR && PN_TUPLE_LEN(A1(lhs)) == 1)
              {
                lhs = (struct PNSource *)PN_TUPLE_AT(A1(lhs), 0);
                if (T(lhs) == AST_MESSAGE || T(lhs) == AST_VALUE) {
                  PN_OP op; op.a = A1(lhs);
                  if (!PN_IS_PTR(A1(lhs)) && A1(lhs) == (PN)op.a) {
                    // TODO hsq 立即数作为命名参数的名字有意义？这行注释掉测试也能通过。
                    PN_ASM2(OP_LOADPN, sreg, A1(lhs));
                  } else {
                    PN_SIZE num = PN_PUT(f->values, A1(lhs));
                    PN_ASM2(OP_LOADVAL, sreg, num);
                  }
                  lhs = NULL;
                }
              }

              if (lhs != NULL)
                potion_source_asmb(P, f, loop, 0, (struct PNSource *)lhs, sreg);

              PN_ASM2(OP_NAMED, freg - 1, sreg + 1);
              PN_REG(f, sreg + 1);
            } else
              potion_source_asmb(P, f, loop, 0, (struct PNSource *)v, *reg);
          } else // TODO hsq inc=0 时所有参数都加载到 reg ？
            potion_source_asmb(P, f, loop, 0, (struct PNSource *)v, *reg);
        });
      }
    } else {
      if (inc) (*reg)++;
      potion_source_asmb(P, f, loop, 0, (struct PNSource *)args, *reg);
    }
  } else {
    if (inc) (*reg)++;
    PN_ASM2(OP_LOADPN, *reg, args);
  }
}

// 编译 PNSource t -> PNProto f ，填充 f 、指令码写入 f.ops ； reg 是当前指令码的寄存器，
//    当 t 节点是循环时 loop 非空， 对独立的消息表达式和赋值的左值表达式 count 为各项的索引。
// TODO hsq 调整参数顺序，主要的、常用的放在前边？如 loop 和 count 放最后？以及 potion_arg_asmb 。
// TODO hsq 为什么连续多个指令使用相同的 reg ？如 count 非 0 时？
// TODO hsq 参考操作码的含义，重新研究各种语法如何生成操作码，并测试。
void potion_source_asmb(Potion *P, vPN(Proto) f, struct PNLoop *loop, PN_SIZE count,
                        vPN(Source) t, u8 reg) {
  PN_REG(f, reg);

  // TODO hsq 精简的语法、语义（尤其是表结构还用做块参数表和子表达式）导致编译逻辑太复杂？
  switch (T(t)) {
    // 遍历元组，每语句单独编译
    case AST_CODE:
    case AST_BLOCK: {
      if (AT1 != PN_NIL) {
        // TODO hsq 对 potion_source_asmb 的调用，可大部分改成 goto 实现？用宏替换。
        PN_TUPLE_EACH(AT1, i, v, {
          potion_source_asmb(P, f, loop, 0, (struct PNSource *)v, reg);
        });
      }
      break;
    }

    // 遍历元组，每语句单独编译，赋予编号。
    case AST_EXPR: {
      // TODO hsq 不可能为空。
      assert(AT1 && PN_TUPLE_LEN(AT1) > 0);
      // if (AT1 != PN_NIL) {
        PN_TUPLE_EACH(AT1, i, v, {
          potion_source_asmb(P, f, loop, i, (struct PNSource *)v, reg);
        });
      // }
      break;
    }

    // 编译子函数。
    case AST_PROTO:
      PN_BLOCK(reg, AT2, AT1);
      break;

    // 如果参数不是指针且可放入指令中，则生成 LOADPN ，否则将指针加入 .values 并生成 LOADVAL 。
    // 如果多于一个参数，则后续参数为函数参数表和代码块。
    case AST_VALUE: {
      PN_OP op; op.a = AT1;
      if (!PN_IS_PTR(AT1) && AT1 == (PN)op.a) {
        PN_ASM2(OP_LOADPN, reg, AT1);
      } else {
        PN_SIZE num = PN_PUT(f->values, AT1);
        PN_ASM2(OP_LOADVAL, reg, num);
      }
      // TODO hsq 当前只有一个参数，下面代码注释掉也能通过测试。
      // if (AT2 != PN_NIL) {
      //   u8 breg = reg;
      //   PN_ASM1(OP_SELF, ++breg);
      //   PN_ARG_TABLE(AT2, breg, 1);
      //   if (AT3 != PN_NIL) {
      //     breg++;
      //     PN_BLOCK(breg, AT3, PN_NIL);
      //   }
      //   PN_ASM2(OP_CALL, reg, breg);
      // }
    }
    break;

    // 处理 upval 、本地变量、对象属性的赋值。
    case AST_ASSIGN: {
      vPN(Source) lhs = (struct PNSource *)AT1;
      PN_SIZE num = PN_NONE, c = count;
      u8 opcode = OP_GETUPVAL, breg = reg;

      if (T(lhs) == AST_EXPR) {
        unsigned long i = 0;
        c = PN_TUPLE_LEN(A1(lhs)) - 1;
        for (i = 0; i < c; i++) {
          potion_source_asmb(P, f, loop, i, (struct PNSource *)PN_TUPLE_AT(A1(lhs), i), reg);
        };
        lhs = (struct PNSource *)PN_TUPLE_AT(A1(lhs), c);
      }

      // TODO hsq AST_QUERY 可以作为赋值表达式的左值？意思是存在才赋值？以及 AST_PATHQ 。
      if (T(lhs) == AST_MESSAGE || T(lhs) == AST_QUERY) {
        if (c == 0) {
          num = PN_UPVAL(A1(lhs));
          if (num == PN_NONE) {
            num = PN_PUT(f->locals, A1(lhs));
            opcode = OP_GETLOCAL;
          }
        } else {
          num = PN_PUT(f->values, A1(lhs));
          PN_ASM2(OP_LOADVAL, ++breg, num);
          opcode = OP_DEF;
          num = ++breg;
        }
      } else if (T(lhs) == AST_PATH || T(lhs) == AST_PATHQ) {
        num = PN_PUT(f->values, A1(lhs));
        if (c == 0) {
          PN_PUT(f->paths, PN_NUM(num));
          PN_ASM1(OP_SELF, reg);
        }
        PN_ASM2(OP_LOADVAL, ++breg, num);
        opcode = OP_GETPATH;
        num = ++breg;
      }

      if (A2(lhs) != PN_NIL) {
        breg = reg;
        PN_ASM2(opcode, ++breg, num);
        PN_ASM2(OP_CALLSET, reg, breg);
        PN_ARG_TABLE(A2(lhs), breg, 1);
        // TODO: no block allowed here?
        potion_source_asmb(P, f, loop, 0, (struct PNSource *)AT2, ++breg);
        PN_ASM2(OP_CALL, reg, breg);
      } else {
        potion_source_asmb(P, f, loop, 0, (struct PNSource *)AT2, breg);
        if (opcode == OP_GETUPVAL) {
          if (T(lhs) == AST_QUERY) {
            PN_ASM2(OP_GETUPVAL, breg, num);
            PN_ASM2(OP_TESTJMP, breg, 1);
          }
          PN_ASM2(OP_SETUPVAL, reg, num);
        } else if (opcode == OP_GETLOCAL) {
          if (T(lhs) == AST_QUERY) {
            PN_ASM2(OP_GETLOCAL, breg, num);
            PN_ASM2(OP_TESTJMP, breg, 1);
          }
          PN_ASM2(OP_SETLOCAL, reg, num);
        } else if (opcode == OP_GETPATH) {
          if (T(lhs) == AST_PATHQ) {
            PN_ASM2(OP_GETPATH, reg, num);
            PN_ASM2(OP_TESTJMP, reg, 1);
          }
          PN_ASM2(OP_SETPATH, reg, num);
        } else {
          PN_ASM2(opcode, reg, num);
        }
      }
      PN_REG(f, breg);
    }
    break;

    // 编译自增、自减操作；会收集 .locals 或 .upvals 。
    // TODO hsq 可优化？现在使用2、3个寄存器，4、5条指令。
    case AST_INC: {
      u8 breg = reg;
      vPN(Source) lhs = (struct PNSource *)AT1;
      PN_SIZE num = PN_UPVAL(A1(lhs));
      u8 opcode = OP_SETUPVAL;
      if (num == PN_NONE) {
        num = PN_PUT(f->locals, A1(lhs));
        opcode = OP_SETLOCAL;
      }

      // TODO hsq num 只是本地 .upvals 中的索引，如何找到、读取、更新实际位置？
      if (opcode == OP_SETUPVAL)
        PN_ASM2(OP_GETUPVAL, reg, num);
      else if (opcode == OP_SETLOCAL)
        PN_ASM2(OP_GETLOCAL, reg, num);
      if (PN_IS_NUM(AT2)) {
        breg++;
        PN_ASM2(OP_MOVE, breg, reg);
      }
      PN_ASM2(OP_LOADPN, breg + 1, (AT2 | PN_FNUMBER));
      PN_ASM2(OP_ADD, breg, breg + 1);
      PN_ASM2(opcode, breg, num);
      PN_REG(f, breg + 1);
    }
    break;

    // 编译二元比较、算数、位运算：两操作数依次编译到当前和下一个寄存器，
    //    然后加入运算符操作它们。
    case AST_CMP:  case AST_EQ:    case AST_NEQ:   case AST_GT:    case AST_GTE:
                   case AST_LT:    case AST_LTE:
    case AST_PLUS: case AST_MINUS: case AST_TIMES: case AST_DIV:
                   case AST_REM:   case AST_POW:
    case AST_BITL: case AST_BITR:  case AST_PIPE:  case AST_CARET: case AST_AMP:{
      PN_ARG(0, reg);
      PN_ARG(1, reg + 1);
      switch (T(t)) {
        // TODO hsq 双参数寄存器挨着，可用单参数指令码？如果没有更灵活的需求。
        case AST_CMP:   PN_ASM2(OP_CMP,   reg, reg + 1); break;
        case AST_EQ:    PN_ASM2(OP_EQ,    reg, reg + 1); break;
        case AST_NEQ:   PN_ASM2(OP_NEQ,   reg, reg + 1); break;
        case AST_GTE:   PN_ASM2(OP_GTE,   reg, reg + 1); break;
        case AST_GT:    PN_ASM2(OP_GT,    reg, reg + 1); break;
        case AST_LT:    PN_ASM2(OP_LT,    reg, reg + 1); break;
        case AST_LTE:   PN_ASM2(OP_LTE,   reg, reg + 1); break;
        case AST_PLUS:  PN_ASM2(OP_ADD,   reg, reg + 1); break;
        case AST_MINUS: PN_ASM2(OP_SUB,   reg, reg + 1); break;
        case AST_TIMES: PN_ASM2(OP_MULT,  reg, reg + 1); break;
        case AST_DIV:   PN_ASM2(OP_DIV,   reg, reg + 1); break;
        case AST_REM:   PN_ASM2(OP_REM,   reg, reg + 1); break;
        case AST_POW:   PN_ASM2(OP_POW,   reg, reg + 1); break;
        case AST_BITL:  PN_ASM2(OP_BITL,  reg, reg + 1); break;
        case AST_BITR:  PN_ASM2(OP_BITR,  reg, reg + 1); break;
        case AST_PIPE:  PN_ASM2(OP_PIPE,  reg, reg + 1); break;
        case AST_CARET: PN_ASM2(OP_CARET, reg, reg + 1); break;
        case AST_AMP:   PN_ASM2(OP_AMP,   reg, reg + 1); break;
      }
    }
    break;

    // 编译操作数，加入运算符。
    case AST_NOT: case AST_WAVY:
      PN_ARG(0, reg);
      PN_ASM1(T(t) == AST_WAVY ? OP_BITN : OP_NOT, reg);
    break;

    // 编译操作数1、加入条件跳转、编译操作数2。
    case AST_AND: case AST_OR: {
      int jmp;
      PN_ARG(0, reg);
      jmp = PN_OP_LEN(f);
      PN_ASM2(T(t) == AST_AND ? OP_NOTJMP : OP_TESTJMP, reg, 0);
      PN_ARG(1, reg);
      // VM 中执行指令时会自动 +1 ，所以这里从相对位置中 -1 。
      PN_OP_AT(f, jmp).b = (PN_OP_LEN(f) - jmp) - 1;
    }
    break;

    // 编译 if else elsif class while loop return break continue self 等关键字。
    // TODO: this stuff is ugly and repetitive
    case AST_MESSAGE:
    // TODO hsq 这两个分开比较好？大部分代码都是 AST_MESSAGE 的。
    case AST_QUERY: {
      u8 breg = reg;
      // TODO hsq 未检查语法，因此可写出不带 if 的 else 代码块。
      // TODO hsq elsif 实现为 else: if XXX. ？
      // TODO hsq else 和 elsif 不依赖 if-test ，而是每个 body 最后都无条件跳转？
      if (T(t) == AST_MESSAGE && AT1 == $(if)) {
        int jmp; breg++;
        PN_ARG_TABLE(AT2, breg, 0);
        jmp = PN_OP_LEN(f);
        PN_ASM2(OP_NOTJMP, breg, 0);
        // TODO hsq if-test 先运行，但其寄存器在 if-body 后，如何保证不被覆盖？
        //    因为当前实现中，后续 else 和 elsif 依赖该寄存器做判断。
        potion_source_asmb(P, f, loop, 0, (struct PNSource *)AT3, reg);
        PN_OP_AT(f, jmp).b = (PN_OP_LEN(f) - jmp) - 1;
      } else if (T(t) == AST_MESSAGE && (AT1 == $(elsif) || AT1 == $(elseif))) {
        int jmp1 = PN_OP_LEN(f), jmp2; breg++;
        PN_ASM2(OP_TESTJMP, breg, 0);
        PN_ARG_TABLE(AT2, breg, 0);
        jmp2 = PN_OP_LEN(f);
        PN_ASM2(OP_NOTJMP, breg, 0);
        potion_source_asmb(P, f, loop, 0, (struct PNSource *)AT3, reg);
        PN_OP_AT(f, jmp1).b = (PN_OP_LEN(f) - jmp1) - 1;
        PN_OP_AT(f, jmp2).b = (PN_OP_LEN(f) - jmp2) - 1;
      } else if (T(t) == AST_MESSAGE && AT1 == $(else)) {
        int jmp = PN_OP_LEN(f); breg++;
        PN_ASM2(OP_TESTJMP, breg, 0);
        potion_source_asmb(P, f, loop, 0, (struct PNSource *)AT3, reg);
        PN_OP_AT(f, jmp).b = (PN_OP_LEN(f) - jmp) - 1;
      } else if (T(t) == AST_MESSAGE && AT1 == $(class)) {
        u8 breg = reg;
        if (count == 0)
          PN_ASM1(OP_SELF, reg);
        if (AT3 != PN_NIL) {
          // TODO: a hack to make sure constructors always return self
          // TODO hsq 先检查最后是否有 self ？
          PN ctor = A1(AT3);
          PN_PUSH(ctor, PN_AST1(AST_EXPR, PN_TUP(PN_AST1(AST_MESSAGE, PN_LITERAL("self")))));
          breg++;
          PN_BLOCK(breg, AT3, AT2);
        }
        PN_ASM2(OP_CLASS, reg, breg);
      } else if (T(t) == AST_MESSAGE && (AT1 == $(while) || AT1 == $(loop))) {
        // TODO hsq 使用有意义的命名后缀代替数字后缀？
        int jmp1 = 0, jmp2 = PN_OP_LEN(f); breg++;
        struct PNLoop l; l.bjmpc = 0; l.cjmpc = 0;
        int i;
        if (AT1 == $(while)) {
          // TODO: error if args to `loop`?
          PN_ARG_TABLE(AT2, breg, 0);
          jmp1 = PN_OP_LEN(f);
          PN_ASM2(OP_NOTJMP, breg, 0);
        }
        potion_source_asmb(P, f, &l, 0, (struct PNSource *)AT3, reg);
        PN_ASM2(OP_JMP, 0, (jmp2 - PN_OP_LEN(f)) - 1);
        if (AT1 == $(while)) {
          PN_OP_AT(f, jmp1).b = (PN_OP_LEN(f) - jmp1) - 1;
        }
        for (i = 0; i < l.bjmpc; i++) {
          PN_OP_AT(f, l.bjmps[i]).b = (PN_OP_LEN(f) - l.bjmps[i]) - 1;
        }
        for (i = 0; i < l.cjmpc; i++) {
          PN_OP_AT(f, l.cjmps[i]).b = (jmp2 - l.cjmps[i]) - 1;
        }
      } else if (T(t) == AST_MESSAGE && AT1 == $(return)) {
        // TODO hsq "return"->nil, "return (...)"->非空则为最后一项，
        //    "return ()"->取决于当前 reg 中的值。
        PN_ARG_TABLE(AT2, reg, 0);
        PN_ASM1(OP_RETURN, reg);
      } else if (T(t) == AST_MESSAGE && AT1 == $(break)) {
        if (loop != NULL) {
          // TODO hsq 检查是否越界 MAX_JUMPS ，以及下面的 continue 。或者先统计所需数量？
          loop->bjmps[loop->bjmpc++] = PN_OP_LEN(f);
          PN_ASM2(OP_JMP, 0, 0);
        } else {
          // TODO: Report error: 'break' outside of loop.
        }
      } else if (T(t) == AST_MESSAGE && AT1 == $(continue)) {
        if (loop != NULL) {
          loop->cjmps[loop->cjmpc++] = PN_OP_LEN(f);
          PN_ASM2(OP_JMP, 0, 0);
        } else {
          // TODO: Report error: 'continue' outside of loop.
        }
      } else if (T(t) == AST_MESSAGE && AT1 == $(self)) {
        PN_ASM1(OP_SELF, reg);
      } else {
        // TODO hsq 太复杂、繁琐。
        u8 opcode = OP_GETUPVAL;
        PN_SIZE num = PN_NONE;
        if (count == 0 && T(t) == AST_MESSAGE) {
          num = PN_UPVAL(AT1);
          if (num == PN_NONE) {
            num = PN_GET(f->locals, AT1);
            opcode = OP_GETLOCAL;
          }
        }

        if (num == PN_NONE && AT1 != PN_NIL) {
          u8 oreg = ++breg;
          int jmp = 0;
          num = PN_PUT(f->values, AT1);
          if (count == 0) {
            PN_ASM1(OP_SELF, oreg);
          } else {
            PN_ASM2(OP_MOVE, oreg, reg);
          }
          PN_ASM2(OP_LOADVAL, reg, num);
          PN_ASM2(AT2 != PN_NIL || AT3 != PN_NIL ? OP_MESSAGE : OP_BIND, reg, breg);
          if (T(t) == AST_QUERY && AT2 != PN_NIL) {
            jmp = PN_OP_LEN(f);
            PN_ASM2(OP_NOTJMP, reg, 0);
          }
          if (AT3 == PN_NIL) {
            PN_ARG_TABLE(AT2, breg, 1);
          } else {
            breg++;
            PN_BLOCK(breg, AT3, AT2);
          }
          if (T(t) == AST_MESSAGE) {
            PN_ASM2(OP_CALL, reg, breg);
          } else
            if (AT2 != PN_NIL) {
              PN_ASM2(OP_CALL, reg, breg);
              PN_OP_AT(f, jmp).b = (PN_OP_LEN(f) - jmp) - 1;
            } else {
              PN_ASM1(OP_TEST, reg);
            }
        } else { // num != PN_NONE || AT1 == PN_NIL 肯定是 AST_MESSAGE 。
          int arg = (AT2 != PN_NIL);
          int call = (AT3 != PN_NIL || arg);
          if (num != PN_NONE)
            PN_ASM2(opcode, reg, num);
          if (call) {
            PN_ASM1(OP_SELF, ++breg);
            PN_ARG_TABLE(AT2, breg, 1);
            if (AT3 != PN_NIL) {
              breg++;
              PN_BLOCK(breg, AT3, PN_NIL);
            }
            PN_ASM2(OP_CALL, reg, breg);
          }
        }
      }
      PN_REG(f, breg);
    }
    break;

    // 编译 /PATH 和 ? /PATH ：path 放入 .values ，如非调用则取缺省对象。。。
    case AST_PATH:
    case AST_PATHQ: {
      PN_SIZE num = PN_PUT(f->values, AT1);
      // TODO hsq 如处于消息链中，则接收者不在 reg 中？ GETPATH 如何运行？
      if (count == 0) {
        PN_PUT(f->paths, PN_NUM(num));
        PN_ASM1(OP_SELF, reg);
      }
      // TODO hsq 尽量在编译时处理 类成员、 quiz 运算符等？
      PN_ASM2(OP_LOADVAL, reg + 1, num);
      PN_ASM2(OP_GETPATH, reg, reg + 1);
      if (T(t) == AST_PATHQ)
        PN_ASM1(OP_TEST, reg);
      PN_REG(f, reg + 1);
    }
    break;

    // 依次编译 lick 各部分到 reg 及其后的寄存器，最后加入 NEWLICK 指令。
    // lick 的 name 部分放入 .values 。
    case AST_LICK: {
      u8 breg = reg;
      PN_SIZE num = PN_PUT(f->values, AT1);
      PN_ASM2(OP_LOADVAL, reg, num);
      if (AT2 != PN_NIL)
        potion_source_asmb(P, f, loop, 0, (struct PNSource *)AT2, ++breg);
      else if (AT3 != PN_NIL)
        PN_ASM2(OP_LOADPN, ++breg, PN_NIL);
      if (AT3 != PN_NIL)
        potion_source_asmb(P, f, loop, 0, (struct PNSource *)AT3, ++breg);
      PN_ASM2(OP_NEWLICK, reg, breg);
      // PN_REG(f, breg);
      PN_REG(f, reg + 2); // 为了便于 JIT 生成汇编码
    }
    break;

    // 编译表：构造，依次设置键值对和单值。
    case AST_TABLE: {
      PN_ASM1(OP_NEWTUPLE, reg);
      if (AT1 != PN_NIL) {
        PN_TUPLE_EACH(AT1, i, v, {
          if (T(v) == AST_ASSIGN) {
            vPN(Source) lhs = (struct PNSource *)A1(v);
            // 对 message 形式的 key 特殊处理，否则会被下面的代码作为 变量。
            if (T(lhs) == AST_EXPR && PN_TUPLE_LEN(A1(lhs)) == 1)
            {
              lhs = (struct PNSource *)PN_TUPLE_AT(A1(lhs), 0);
              if (T(lhs) == AST_MESSAGE) {
                PN_SIZE num = PN_PUT(f->values, A1(lhs));
                PN_ASM2(OP_LOADVAL, reg + 1, num);
                lhs = NULL;
              }
            }

            if (lhs != NULL)
              potion_source_asmb(P, f, loop, 0, (struct PNSource *)lhs, reg + 1);

            potion_source_asmb(P, f, loop, 0, (struct PNSource *)A2(v), reg + 2);
            PN_ASM2(OP_SETTABLE, reg, reg + 2);
            PN_REG(f, reg + 2);
          } else {
            potion_source_asmb(P, f, loop, 0, (struct PNSource *)v, reg + 1);
            PN_ASM2(OP_SETTUPLE, reg, reg + 1);
            PN_REG(f, reg + 1);
          }
        });
      }
      break;
    }

    default: FAIL_MSG("invalid AST TYPE[code: %d]", T(t)); assert(0);
  }
}

// 编译函数参数表 PNSource src -> tuple 并返回，格式：(name, 'o'编码, ...)；
//    并且逐个填充到 PNProto.locals 。
PN potion_sig_compile(Potion *P, vPN(Proto) f, PN src) {
  PN sig = PN_TUP0();
  vPN(Source) t = (struct PNSource *)src;
  if (T(t) == AST_TABLE && AT1 != PN_NIL) {
    PN_TUPLE_EACH(AT1, i, v, {
      vPN(Source) expr = (struct PNSource *)v;
      PN typ = PN_NUM('o');
      if (T(expr) == AST_ASSIGN) {
        // TODO hsq 当前 rhs 是什么？类型标识符？缺省值？
        // vPN(Source) rhs = (struct PNSource *)A2(expr);
        // assert(T(rhs) == AST_EXPR);
        expr = (struct PNSource *)A1(expr); // lhs
      }
      assert(T(expr) == AST_EXPR && PN_TUPLE_LEN(A1(expr)) == 1);
      expr = (struct PNSource *)PN_TUPLE_AT(A1(expr), 0); // var_name
      // TODO hsq 解析语法时进行限制，以及上面的 assert ，提示语法错误？
      assert(T(expr) == AST_MESSAGE);
      PN_PUT(f->locals, A1(expr));
      sig = PN_PUSH(PN_PUSH(sig, A1(expr)), typ);
    });
  }
  return sig;
}

// 编译 PNSource self -> PNProto | nil ；
//  成功则附加 OP_RETURN 0 指令，返回值取决于该寄存器的值，如之前有 return 则无效。
//  source 是源码文件名或外层 PNProto ， sig 是函数参数表签名；
//  self.type 不是 AST_CODE 或 AST_BLOCK 则返回 nil 。
PN_MSG(potion_source_compile, PN source, PN sig) {
  vPN(Proto) f;
  vPN(Source) t = (struct PNSource *)self;
  // TODO hsq 适时（解析时或解析后）优化调整 PNSource ？
  //    以及生成的字节码，如 lpeg 。
  //    如同一寄存器刚 set 则无需 get ，同一个 self 反复 set 、覆盖等，都不符合局部性。

  switch (T(t)) {
    case AST_CODE:
    case AST_BLOCK: break;
    default: return PN_NIL; // TODO: error
  }

  f = PN_ALLOC(PN_TPROTO, struct PNProto);
  f->flags     = 0;
  f->source    = source;
  f->stacksize = 1;
  f->protos    = PN_TUP0();
  f->paths     = PN_TUP0();
  f->locals    = PN_TUP0();
  f->upvals    = PN_TUP0();
  f->values    = PN_TUP0();
  f->tree      = self;
  f->sig       = (sig == PN_NIL ? PN_TUP0() : potion_sig_compile(P, f, sig));
  f->ops       = (PN)potion_asm_new(P);

  potion_source_asmb(P, f, NULL, 0, t, 0);
  PN_ASM1(OP_RETURN, 0);

  return (PN)f;
}

// TODO hsq 这些宏无需参数 ptr 和 pn ，以及下面的 WRITE_XXX 。
#define READ_U8(ptr) ({u8 rpu = *ptr; ptr += sizeof(u8); rpu;})
// 读取 pn 字节，返回 PN 。
#define READ_PN(pn, ptr) ({PN rpn = *(PN *)ptr; ptr += pn; rpn;})
// 读取一个 PN ，如果是指针则再读取并构造字符串或小数并返回 PN 。
#define READ_CONST(pn, ptr) ({ \
    PN val = READ_PN(pn, ptr); \
    if (PN_IS_PTR(val)) { \
      size_t len = (val >> 4) - 1; \
      if (val & 2) { \
        val = potion_decimal(P, (char *)ptr, len); \
      } else { \
        val = potion_str2(P, (const char *)ptr, len); \
      } \
      ptr += len; \
    } \
    val; \
  })
// 读取元组项数，构造元组，逐项解析、填充。
#define READ_TUPLE(ptr) \
  long i = 0, count = READ_U8(ptr); \
  PN tup = potion_tuple_with_size(P, (PN_SIZE)count); \
  for (; i < count; i++)
// 解析元组并返回。
#define READ_VALUES(pn, ptr) ({ \
    READ_TUPLE(ptr) PN_TUPLE_AT(tup, i) = READ_CONST(pn, ptr); \
    tup; \
  })
// 解析原型元组并返回。
#define READ_PROTOS(pn, ptr) ({ \
    READ_TUPLE(ptr) PN_TUPLE_AT(tup, i) = potion_proto_load(P, (PN)f, pn, &(ptr)); \
    tup; \
  })

// 加载、解析字节码 PNBytes buf -> PNProto ； .tree 丢失 .jit 不在此时处理。
// TODO: this byte string is volatile, need to avoid using ptr
PN potion_proto_load(Potion *P, PN up, u8 pn, u8 **ptr) {
  PN len = 0;
  PNAsm * volatile ops = NULL;
  vPN(Proto) f = PN_ALLOC(PN_TPROTO, struct PNProto);
  f->flags     = READ_U8(*ptr);
  IS_LOADED_SET(f, 1);
  f->source    = READ_CONST(pn, *ptr);
  if (f->source == PN_NIL) f->source = up;
  f->sig       = READ_VALUES(pn, *ptr);
  f->stacksize = PN_INT(READ_CONST(pn, *ptr));
  f->values    = READ_VALUES(pn, *ptr);
  f->paths     = READ_VALUES(pn, *ptr);
  f->locals    = READ_VALUES(pn, *ptr);
  f->upvals    = READ_VALUES(pn, *ptr);
  f->protos    = READ_PROTOS(pn, *ptr);

  len = READ_PN(pn, *ptr);
  PN_FLEX_NEW(ops, PN_TBYTES, PNAsm, len);
  PN_MEMCPY_N(ops->ptr, *ptr, u8, len);
  ops->len = len;

  f->ops = (PN)ops;
  *ptr += len;
  return (PN)f;
}

// 加载、解析字节码 PNBytes buf -> PNProto ，失败则返回 nil 。
// TODO hsq 字节码是否跨平台？使用编译平台的 PN 长度加载是否足够？
// TODO: load from a stream
PN potion_source_load(Potion *P, PN buf) {
  u8 *ptr;
  vPN(BHeader) h = (struct PNBHeader *)PN_STR_PTR(buf);
  if ((size_t)PN_STR_LEN(buf) <= sizeof(struct PNBHeader) ||
      strncmp((char *)h->sig, POTION_SIG, POTION_SIG_LEN) != 0)
    return PN_NIL;

  ptr = h->proto;
  return potion_proto_load(P, PN_NIL, h->pn, &ptr);
}

#define WRITE_U8(un, ptr) ({*ptr = (u8)un; ptr += sizeof(u8);})
#define WRITE_PN(pn, ptr) ({*(PN *)ptr = pn; ptr += sizeof(PN);})
// 把 val 写入缓冲区 ptr ：立即数直接写入(PN)，
//  字符串则依次写入字节数编码(PN)和字符串，
//  小数先字符串化再写入，其他指针作为 nil 写入。
#define WRITE_CONST(val, ptr) ({ \
    if (PN_IS_STR(val)) { \
      PN count = (PN_STR_LEN(val)+1) << 4; \
      WRITE_PN(count, ptr); \
      PN_MEMCPY_N(ptr, PN_STR_PTR(val), char, PN_STR_LEN(val)); \
      ptr += PN_STR_LEN(val); \
    } else if (PN_IS_DECIMAL(val)) { \
      PN str = potion_num_string(P, PN_NIL, val); \
      PN count = ((PN_STR_LEN(str)+1) << 4) | 2; \
      WRITE_PN(count, ptr); \
      PN_MEMCPY_N(ptr, PN_STR_PTR(str), char, PN_STR_LEN(str)); \
      ptr += PN_STR_LEN(str); \
    } else { \
      PN cval = (PN_IS_PTR(val) ? PN_NIL : val); \
      WRITE_PN(cval, ptr); \
    } \
  })
// 把元组 tup 序列化到缓冲区 ptr ：先写入项数(u8)，再逐项写入。
#define WRITE_TUPLE(tup, ptr) \
  long i = 0, count = PN_TUPLE_LEN(tup); \
  /* TODO hsq 极端情况可能不够？ */ \
  assert(count <= UINT8_MAX); \
  WRITE_U8(count, ptr); \
  for (; i < count; i++)
// 遍历元组 tup ，写入每一项。
#define WRITE_VALUES(tup, ptr) ({ \
    WRITE_TUPLE(tup, ptr) WRITE_CONST(PN_TUPLE_AT(tup, i), ptr); \
  })
// 遍历原型元组 tup ，写入每一项原型。
#define WRITE_PROTOS(tup, ptr) ({ \
    WRITE_TUPLE(tup, ptr) ptr += potion_proto_dump(P, PN_TUPLE_AT(tup, i), \
        out, (char *)ptr - PN_STR_PTR(out)); \
  })

// 把 PNProto proto 写入 PNBytes out 的位置 pos ，返回写入字节数：
//  依次写入 .flags .source .sig .stack .values .paths .locals .upvals .protos .ops ，
//  未写入 .vt .uniq .tree .jit 。
long potion_proto_dump(Potion *P, PN proto, PN out, long pos) {
  vPN(Proto) f = (struct PNProto *)proto;
  char *start = PN_STR_PTR(out) + pos;
  u8 *ptr = (u8 *)start;
  WRITE_U8(f->flags,        ptr);
  WRITE_CONST(f->source,    ptr);
  WRITE_VALUES(f->sig,      ptr);
  WRITE_CONST(PN_NUM(f->stacksize), ptr);
  WRITE_VALUES(f->values,   ptr);
  WRITE_VALUES(f->paths,    ptr);
  WRITE_VALUES(f->locals,   ptr);
  WRITE_VALUES(f->upvals,   ptr);
  WRITE_PROTOS(f->protos,   ptr);
  WRITE_PN(PN_FLEX_SIZE(f->ops), ptr);
  PN_MEMCPY_N(ptr, ((PNFlex *)f->ops)->ptr, u8, PN_FLEX_SIZE(f->ops));
  ptr += PN_FLEX_SIZE(f->ops);
  return (char *)ptr - start;
}

// 序列化 PNProto proto -> PNBytes ：先标头。
// TODO hsq 字节码似乎会有很多 0 ，引入 sproto 中的压缩？
// TODO hsq 加入 memzip 模块，或其他格式，以支持从中加载字节码、源码？
// TODO: dump to a stream
PN potion_source_dump(Potion *P, PN proto) {
  PN pnb = potion_bytes(P, 8192);
  struct PNBHeader h;
  PN_MEMCPY_N(h.sig, POTION_SIG, u8, POTION_SIG_LEN);
  h.major = POTION_MAJOR;
  h.minor = POTION_MINOR;
  h.vmid = POTION_VMID;
  h.pn = (u8)sizeof(PN);

  PN_MEMCPY(PN_STR_PTR(pnb), &h, struct PNBHeader);
  PN_STR_LEN(pnb) = (long)sizeof(struct PNBHeader) +
    potion_proto_dump(P, proto, pnb, sizeof(struct PNBHeader));
  return pnb;
}

// 以 Lobby 为命名空间调用 PNProto code ，无参，返回其结果。
PN potion_run(Potion *P, PN code) {
  #if POTION_JIT == 1
    if (P->jit_on) {
      PN cl = potion_closure_new(P, (PN_F)potion_jit_proto(P, code), PN_NIL, 1);
      PN_CLOSURE(cl)->data[0] = code;
      PN_F jit = JIT_CODE(PN_PROTO(code)->jit);
      return jit(P, cl, P->lobby);
    }
  #endif
  return potion_vm(P, code, P->lobby, PN_NIL, 0, NULL);
}

// 解析 PNBytes bytes -> PNSource ，后编译、运行，返回其结果 ｜ error 。
PN potion_eval(Potion *P, PN bytes) {
  PN code = potion_parse(P, bytes);
  if (PN_TYPE(code) != PN_TSOURCE) return code;
  code = potion_send(code, $(compile), PN_NIL, PN_NIL);
  // TODO hsq 需要检查 code 是否 nil 等？
  return potion_run(P, code);
}

// 注册虚表 proto 的消息。
void potion_compiler_init(Potion *P) {
  PN pro_vt = PN_VTABLE(PN_TPROTO);
  PN_METHOD(pro_vt, "call",   potion_proto_call,   "|args=t");
  PN_METHOD(pro_vt, "tree",   potion_proto_tree,   0);
  PN_METHOD(pro_vt, "string", potion_proto_string, 0);
  #if POTION_JIT == 1
  PN_METHOD(pro_vt, "disasm", potion_jit_disasm,   0);
  #endif

  PN src_vt = PN_VTABLE(PN_TSOURCE);
  PN_METHOD(src_vt, "compile", potion_source_compile, "|source=S,sig=o.outer=o,sig=o"); // in compile.c
}
